package system;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.LinkedList;
import java.util.Random;

import org.uncommons.maths.random.ExponentialGenerator;

import item.Samochod;
import kolejka.Kolejka;
import stanowisko.Stanowisko;
import utils.Logger;

public class SystemMM1 {
	private int CARS_NUMBER = 10000;

	private Stanowisko<Samochod> myjnia;
	private Kolejka<Samochod> kolejka;
	private double currentTime;
	
	private Logger log = new Logger(true);

	private LinkedList<Double> arrivalTimes;

	private Random rand;
	ExponentialGenerator expGen;
	
	Double MAX;

	public SystemMM1(Stanowisko<Samochod> myjnia, Kolejka<Samochod> kolejka, double lambda) {
		this.myjnia = myjnia;
		this.kolejka = kolejka;
		this.currentTime = 0;

		this.rand = new Random(System.currentTimeMillis());
		this.expGen = new ExponentialGenerator(lambda, rand);
	}

	public double symulacja() {

		double suma = 0;

		generateArrivalTimes(CARS_NUMBER);
		int id = 0;
		while (!arrivalTimes.isEmpty() || !this.kolejka.czyPusty() ) { // dopoki sa lub beda samochody
			// umiesc samochody w kolejce jesli sie nadaja
			id = addAllAvailableCarsToQueue(id);

			// sprawdzamy czy samochod ktory byl na myjni skonczyl mycie
			if (!this.myjnia.czyPusty()) {
				if(this.currentTime - this.myjnia.dajCzasRozpoczeciaMyciaSamochodu() >= this.myjnia.dajCzasObslugi()-0.0001) {
					suma = suma + this.myjnia.dajCzasRozpoczeciaMyciaSamochodu() - this.myjnia.dajAktualnyObiekt().dajCzasPrzybycia();
					
//					if(this.myjnia.getCurrentItem().getID()  == CARS_NUM) {
//						System.out.println("-M Time: " + this.currentTime + " Suma: " + suma + " Current service time: " + this.myjnia.getServiceTime());
//					}
					
					this.log.logCarWashed(currentTime, 1,  this.myjnia.dajAktualnyObiekt(), kolejka.dajWszystkie());
							
					this.myjnia.usunObiekt();
				} else {// myjnia zajeta i nie skonczyla myc
//					updateCurrentTime();
//					if(this.currentTime == MAX) {
//						break;
//					}
//					continue;
				}
			}

			if (this.myjnia.czyPusty()) {// myjnia jest pusta
				if (!this.kolejka.czyPusty()) {// dodanie samochodu na myjnie
					Samochod car = this.kolejka.dajPierwszy();
					this.kolejka.usunPierwszy();
					this.myjnia.ustawAktualnyObiekt(car);
					this.myjnia.ustawCzasRozpoczeciaMyciaSamochodu(this.currentTime);
					this.log.logCarStartedToWash(this.currentTime, 1, this.myjnia.dajAktualnyObiekt(), kolejka.dajWszystkie());
				}
			}

			updateCurrentTime();
			if(this.currentTime == MAX) {
				break;
			}

		}
//		suma += this.myjnia.getLastCarTime() - this.myjnia.getCurrentItem().getArrivalTime();
//		System.out.println("-M Time: " + this.currentTime + " Suma: " + suma);
		this.log.logCarWashed(this.currentTime, 1,  this.myjnia.dajAktualnyObiekt(), kolejka.dajWszystkie());
		System.out.println("Mean waiting time: " + suma / (CARS_NUMBER-1));
		
		return suma / (CARS_NUMBER-1);
	}

	private int addAllAvailableCarsToQueue(int id) {
		while (!arrivalTimes.isEmpty() && arrivalTimes.getFirst() <= this.currentTime) {
			Samochod car = new Samochod(id++, arrivalTimes.getFirst());
			Samochod beingServiced = null;
			this.kolejka.dodaj(car);
			if(!this.myjnia.czyPusty()) {
				beingServiced = (Samochod) this.myjnia.dajAktualnyObiekt();
			}
				//this.log.logAddItemToQueue(this.currentTime, car, beingServiced, this.kolejka.dajWszystkie());
//				System.out.println("+K Pojawil sie samochod: " + arrivalTimes.getFirst());

			arrivalTimes.removeFirst();
		}
		return id;
	}
	
	private void updateCurrentTime() {		
		MAX = Double.MAX_VALUE;

		double czasZakonczeniaMycia = MAX;
		double czasNadejsciaSamochoduDoKolejki = MAX;
		double czasRozpoczeciaMyciaSamochodu = MAX;
		
		if (!this.myjnia.czyPusty()) {
			czasZakonczeniaMycia = this.myjnia.dajCzasRozpoczeciaMyciaSamochodu() + this.myjnia.dajCzasObslugi();
		}

		if (!this.arrivalTimes.isEmpty()) {
			czasNadejsciaSamochoduDoKolejki = this.arrivalTimes.getFirst();
		}

		if (this.myjnia.czyPusty() && !this.kolejka.czyPusty()) {
			czasRozpoczeciaMyciaSamochodu = this.kolejka.dajPierwszy().dajCzasPrzybycia();
		}

		double tmp = Math.min(czasZakonczeniaMycia, czasNadejsciaSamochoduDoKolejki);
		
		this.currentTime = Math.min(tmp, czasRozpoczeciaMyciaSamochodu);
	}

	private void generateArrivalTimes(int itemsNumber) {
		arrivalTimes = new LinkedList<Double>();
		this.arrivalTimes.add(0.0);
		for (int i = 0; i < itemsNumber; i++) {
			this.arrivalTimes.add(this.arrivalTimes.getLast() + this.expGen.nextValue());
//			System.out.println(i + " arrivalTime: " + this.arrivalTimes.getLast());
			
		}
		MAX = arrivalTimes.getLast();
	}
	
	int comparePreciseDoubles(double firstNumber, double secondNumber, int precission) {
		BigDecimal n1 = new BigDecimal(Double.valueOf(firstNumber));
		BigDecimal n2 = new BigDecimal(Double.valueOf(secondNumber));
		
		n1 = n1.round(new MathContext(precission));
		n2 = n2.round(new MathContext(precission));
		
		return n1.compareTo(n2);
	}
}
